/**
 * @file ssd2828.h
 * @author Zhai Tao (zhaitao.as@outlook.com)
 * @brief SSD2828 portable driver
 * @version 0.1
 * @date 2022-03-01
 *
 * @copyright zhaitao.as@outlook.com (c) 2022
 *
 */

#ifndef __SSD2828_H__
#define __SSD2828_H__

#include "bb_pin.h"
#include "video_sync_polarity.h"
#include "videomodes.h"
#include <stdint.h>

#define SSD2828_DEBUG 1

#define SSD2828_DIR 0xB0
#define SSD2828_VICR1 0xB1
#define SSD2828_VICR2 0xB2
#define SSD2828_VICR3 0xB3
#define SSD2828_VICR4 0xB4
#define SSD2828_VICR5 0xB5
#define SSD2828_VICR6 0xB6
#define SSD2828_CFGR 0xB7
#define SSD2828_VCR 0xB8
#define SSD2828_PCR 0xB9
#define SSD2828_PLCR 0xBA
#define SSD2828_CCR 0xBB
#define SSD2828_PSCR1 0xBC
#define SSD2828_PSCR2 0xBD
#define SSD2828_PSCR3 0xBE
#define SSD2828_PDR 0xBF
#define SSD2828_OCR 0xC0
#define SSD2828_MRSR 0xC1
#define SSD2828_RDCR 0xC2
#define SSD2828_ARSR 0xC3
#define SSD2828_LCR 0xC4
#define SSD2828_ICR 0xC5
#define SSD2828_ISR 0xC6
#define SSD2828_ESR 0xC7
#define SSD2828_DAR1 0xC9
#define SSD2828_DAR2 0xCA
#define SSD2828_DAR3 0xCB
#define SSD2828_DAR4 0xCC
#define SSD2828_DAR5 0xCD
#define SSD2828_DAR6 0xCE
#define SSD2828_HTTR1 0xCF
#define SSD2828_HTTR2 0xD0
#define SSD2828_LRTR1 0xD1
#define SSD2828_LRTR2 0xD2
#define SSD2828_TSR 0xD3
#define SSD2828_LRR 0xD4
#define SSD2828_PLLR 0xD5
#define SSD2828_TR 0xD6
#define SSD2828_TECR 0xD7
#define SSD2828_ACR1 0xD8
#define SSD2828_ACR2 0xD9
#define SSD2828_ACR3 0xDA
#define SSD2828_ACR4 0xDB
#define SSD2828_IOCR 0xDC
#define SSD2828_VICR7 0xDD
#define SSD2828_LCFR 0xDE
#define SSD2828_DAR7 0xDF
#define SSD2828_PUCR1 0xE0
#define SSD2828_PUCR2 0xE1
#define SSD2828_PUCR3 0xE2
#define SSD2828_CBCR1 0xE9
#define SSD2828_CBCR2 0xEA
#define SSD2828_CBSR 0xEB
#define SSD2828_ECR 0xEC
#define SSD2828_VSDR 0xED
#define SSD2828_TMR 0xEE
#define SSD2828_GPIO1 0xEF
#define SSD2828_GPIO2 0xF0
#define SSD2828_DLYA01 0xF1
#define SSD2828_DLYA23 0xF2
#define SSD2828_DLYB01 0xF3
#define SSD2828_DLYB23 0xF4
#define SSD2828_DLYC01 0xF5
#define SSD2828_DLYC23 0xF6
#define SSD2828_ACR5 0xF7
#define SSD2828_RR 0xFF

#define SSD2828_CFGR_HS (1 << 0)
#define SSD2828_CFGR_CKE (1 << 1)
#define SSD2828_CFGR_SLP (1 << 2)
#define SSD2828_CFGR_VEN (1 << 3)
#define SSD2828_CFGR_HCLK (1 << 4)
#define SSD2828_CFGR_CSS (1 << 5)
#define SSD2828_CFGR_DCS (1 << 6)
#define SSD2828_CFGR_REN (1 << 7)
#define SSD2828_CFGR_ECD (1 << 8)
#define SSD2828_CFGR_EOT (1 << 9)
#define SSD2828_CFGR_LPE (1 << 10)
#define SSD2828_CFGR_TXD (1 << 11)

#define SSD2828_VIDEO_MODE_NON_BURST_WITH_SYNC_PULSES (0 << 2)
#define SSD2828_VIDEO_MODE_NON_BURST_WITH_SYNC_EVENTS (1 << 2)
#define SSD2828_VIDEO_MODE_BURST (2 << 2)

#define SSD2828_VIDEO_PIXEL_FORMAT_16BPP 0
#define SSD2828_VIDEO_PIXEL_FORMAT_18BPP_PACKED 1
#define SSD2828_VIDEO_PIXEL_FORMAT_18BPP_LOOSELY_PACKED 2
#define SSD2828_VIDEO_PIXEL_FORMAT_24BPP 3

#define SSD2828_LP_CLOCK_DIVIDER(n) (((n)-1) & 0x3F)

#define SSD2828_SPI_MODE_8BIT4WIRE 0
#define SSD2828_SPI_MODE_8BIT3WIRE 1
#define SSD2828_SPI_MODE_24BIT3WIRE 2
#define SSD2828_SPI_MODE SSD2828_SPI_MODE_8BIT3WIRE

#define SSD2828_NVB_0 (0 << 7)
#define SSD2828_NVB_1 (1 << 7)	// burst模式下 非视频信号在垂直消隐期间传送
#define SSD2828_NVD_HS (0 << 6) //非视频数据将使用HS模式传输
#define SSD2828_NVD_LP (1 << 6) //非视频数据将使用LP模式传输

#define SSD2828_SPEED_LP 0
#define SSD2828_SPEED_HS 1
#define SSD2828_SPEED_VD 2

union ssd2828_CFGR {
	uint16_t val;
	struct ssd2828_CFGR_bits {
		/* b0  HS Mode – 0 – LP mode 1 – HS mode */
		uint16_t HS : 1;

		/* b1 Clock Lane Enable – 0 – Clock lane will enter LP mode,
		1 – Clock lane will enter HS mode for all the cases*/
		uint16_t CKE : 1;

		/* b2 Sleep Mode Enable – 0 – Sleep mode is disabled
			1 – Sleep mode is enabled.  Only the register interface is active*/
		uint16_t SLP : 1;

		/* b3 Video Mode Enable 0 – Video mode is disabled 1 – Video mode is enabled*/
		uint16_t VEN : 1;

		/* b4 HS Clock Disable  0 – HS clock is enabled 1 – HS clock is disabled*/
		uint16_t HCLK : 1;

		/* b5  Clock Source Select 0 – The clock source is tx_clk 1 – The clock source is pclk*/
		uint16_t CSS : 1;

		/* b6 DCS Enable 0 – Generic packet 1 – DCS packet */
		uint16_t DCS : 1;

		/* b7 Read Enable – 0 – Write operation 1 – Read operation*/
		uint16_t REN : 1;

		/* b8 ECC CRC Check Disable – 0 – Enable  1 – Disable*/
		uint16_t ECD : 1;

		/* b9 EOT Packet Enable 0 – Do not send 1 – Send  */
		uint16_t EOT : 1;

		/* b10 Long Packet Enable – 0 – Short Packet 1 – Long Packet*/
		uint16_t LPE : 1;

		/* b11 Transmit Disable 0 – Transmit on 1 – Transmit halt */
		uint16_t TXD : 1;
	} bits;
};

/**
 * @brief
 *
 */
struct ssd2828_config {
	int (*gpio_initial)(void);
	void (*set_reset)(int v);
	void (*set_csx)(int v);
	void (*set_sdi)(int v);
	void (*set_sck)(int v);
	int (*get_sdo)(void);
	void (*set_dcx)(int v);
	// int reset_pin;
	// int csx_pin;
	// int sdi_pin;
	// int sck_pin;
	// int sdo_pin;

	/*
	 * The SSD2828 has its own dedicated clock source 'tx_clk' (connected
	 * to TX_CLK_XIO/TX_CLK_XIN pins), which is necessary at least for
	 * clocking SPI after reset. The exact clock speed is not strictly,
	 * defined, but the datasheet says that it must be somewhere in the
	 * 8MHz - 30MHz range (see "TX_CLK Timing" section). It can be also
	 * used as a reference clock for PLL. If the exact clock frequency
	 * is known, then it can be specified here. If it is unknown, or the
	 * information is not trustworthy, then it can be set to 0.
	 *
	 * If unsure, set to 0.
	 */
	int ssd2828_tx_clk_khz;

	/*
	 * This is not a property of the used LCD panel, but more like a
	 * property of the SSD2828 wiring. See the "SSD2828QN4 RGB data
	 * arrangement" table in the datasheet. The SSD2828 pins are arranged
	 * in such a way that 18bpp and 24bpp configurations are completely
	 * incompatible with each other.
	 *
	 * Depending on the color depth, this must be set to 16, 18 or 24.
	 */
	int ssd2828_color_depth;

	/*********************************************************************/
	/* LCD panel configuration                                           */
	/*********************************************************************/

	/*
	 * The number of lanes in the MIPI DSI interface. May vary from 1 to 4.
	 *
	 * This information can be found in the LCD panel datasheet.
	 */
	int mipi_dsi_number_of_data_lanes;

	/*
	 * Data transfer bit rate per lane. Please note that it is expected
	 * to be higher than the pixel clock rate of the used video mode when
	 * multiplied by the number of lanes. This is perfectly normal because
	 * MIPI DSI handles data transfers in periodic bursts, and uses the
	 * idle time between bursts for sending configuration information and
	 * commands. Or just for saving power.
	 *
	 * The necessary Mbps/lane information can be found in the LCD panel
	 * datasheet. Note that the transfer rate can't be always set precisely
	 * and it may be rounded *up* (introducing no more than 10Mbps error).
	 */
	int mipi_dsi_bitrate_per_data_lane_mbps;

	/*
	 * Setting this to 1 enforces packing of 18bpp pixel data in 24bpp
	 * envelope when sending it over the MIPI DSI link.
	 *
	 * If unsure, set to 0.
	 */
	int mipi_dsi_loosely_packed_pixel_format;

	/*
	 * According to the "Example for system sleep in and out" section in
	 * the SSD2828 datasheet, some LCD panel specific delays are necessary
	 * after MIPI DCS commands EXIT_SLEEP_MODE and SET_DISPLAY_ON.
	 *
	 * For example, Allwinner uses 100 milliseconds delay after
	 * EXIT_SLEEP_MODE and 200 milliseconds delay after SET_DISPLAY_ON.
	 */
	int mipi_dsi_delay_after_exit_sleep_mode_ms;
	int mipi_dsi_delay_after_set_display_on_ms;

	int mipi_dsi_video_mode_operation_type;

	int debug;
	int par_size;
	int par_cnt;
	int speedmode;
	int dcs_swr;
	int cmd_video;
	union ssd2828_CFGR cfgr;
};

int ssd2828_init(
    struct ssd2828_config *cfg,
    struct ctfb_res_modes *mode,
    struct lcd_sync_polarity *pol);

/**
 * @brief Enable/Disable dbg print out
 *
 * @param cfg
 * @param val 0:disable default 1:enable
 */
void ssd2828_debug(struct ssd2828_config *cfg, uint8_t val);

void ssd2828_sleep(struct ssd2828_config *cfg);
void ssd2828_LP(struct ssd2828_config *cfg);
void ssd2828_HS(struct ssd2828_config *cfg);
void ssd2828_Video(struct ssd2828_config *cfg);

void ssd2828_write_data(const struct ssd2828_config *drv, uint8_t dat);
void ssd2828_write_reg(const struct ssd2828_config *cfg, uint8_t regnum, uint16_t val);
uint32_t ssd2828_read_reg(const struct ssd2828_config *cfg, uint8_t regnum);

void ssd2828_DcsShortWrite(struct ssd2828_config *cfg, int len);
void ssd2828_DcsLongWrite(struct ssd2828_config *cfg, int len);
void ssd2828_GenericShortWrite(struct ssd2828_config *cfg, int len);
void ssd2828_GenericLongWrite(struct ssd2828_config *cfg, int len);
int ssd2828_DcsRead(struct ssd2828_config *cfg, uint8_t reg, uint16_t len, uint8_t *p);
int ssd2828_GenericRead(struct ssd2828_config *cfg, uint8_t para0, uint8_t para1, uint16_t len, uint8_t *p);

#endif //__SSD2828_H__
